﻿using System;

using System.Collections.Generic;
using System.Drawing;
using Silvermoon.Controls.Base;
using Silvermoon.Animations;
using Silvermoon.Windows;

namespace Silvermoon.Controls.Beta
{
    /// <summary>
    /// Abstract class for listboxes
    /// </summary>
    public abstract class ListBoxBase : ScrollViewer
    {
        #region const

        /// <summary>
        /// Raise this command on an item to notify the listbox to toggle it's selected state.
        /// </summary>
        public static readonly Command SelectItemCommand = new Command();


        #endregion
        #region fields
        private int oldTopIndex, oldBottomIndex;
        private ListViewItemCollection items;
        private ListBoxContainer container;
        private int itemHeight = 80;
        private ListBoxAnimator animator;
        private StretchTransform containerStretch = new StretchTransform();
        private ItemBase selectedItem;
        private Margin containerMargin;
        private int itemHeightChangedStartIndex = -1;

        #endregion
        #region ctor
        public ListBoxBase()
            : base()
        {
            container = new ListBoxContainer();
            items = new ListViewItemCollection(this);
            VisibleControls.AddLast(container);
            ControlFlags &= ~ControlFlags.CanFocus;
        }

        protected override void Dispose(bool dispose)
        {
            items.Clear();
            base.Dispose(dispose);
        }

        #endregion
        #region props

        public bool AlwaysShowFirstItemHeader
        {
            get { return (ControlFlags & ControlFlags.Internal1) == 0; }
            set
            {
                if (AlwaysShowFirstItemHeader != value)
                {
                    ControlFlags = value ? ControlFlags & ~ControlFlags.Internal1 : ControlFlags | ControlFlags.Internal1;
                    OnAlwaysShowFirstItemHeaderChanged();
                }
            }

        }


        /// <summary>
        /// Gets or sets the margin for the items container.
        /// The default value is Margin.Zero. 
        /// </summary>
        /// <example>
        ///  The value new Margin(0, 0, ScrollViewer.ScrollbarWidth,0) sets the bounds of the items container to not overlap with the scrollbar.
        /// </example>
        public Margin ContainerMargin
        {
            get { return containerMargin ?? Margin.Zero; }
            set
            {
                if (containerMargin != value)
                {
                    containerMargin = value;
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        /// <summary>
        /// Gets or sets whether to squeeze the items when the y offset is outside the normal range.
        /// </summary>
        public bool Squeeze { get; set; }

        /// <summary>
        /// Gets or sets the selected item.
        /// </summary>
        public ItemBase SelectedItem
        {
            get { return selectedItem; }
            set
            {
                if (selectedItem != value)
                {
                    ItemBase oldValue = selectedItem;
                    selectedItem = value;
                    OnSelectedItemChanged(oldValue);
                }
            }
        }

        /// <summary>
        /// If overriden, determines whether to execute <see cref="CanDetachItem"/>.
        /// </summary>
        internal protected virtual bool CanDetach
        {
            get { return DetachItem != null; }
        }

        protected ListBoxAnimator Animator
        {
            get
            {
                if (animator == null) animator = new ListBoxAnimator(this);
                return animator;
            }
        }

        protected ItemGroupBase TopItemGroup { get; private set; }

        /// <summary>
        /// Gets the collection of <see cref="ItemBase"/> for the listbox.
        /// </summary>
        public ListViewItemCollection Items { get { return items; } }

        /// <summary>
        /// Gets the height of the content. If this value is larger than the control's height, a scrollbar becomes available.
        /// </summary>
        /// <value></value>
        public override int VirtualHeight
        {
            get { return items.TotalHeight; }
        }

        /// <summary>
        /// Gets or sets the height for a lazy load item.
        /// </summary>
        public int ItemHeight
        {
            get { return itemHeight; }
            set
            {
                if (value < 0) value = 0;
                if (itemHeight != value)
                {
                    itemHeight = value;
                }
            }
        }

        #endregion
        #region methods

        protected virtual void OnAlwaysShowFirstItemHeaderChanged()
        {
            Invalidate(InvalidationFlag.Layout);
        }


        // TODO: Implement
        /// <summary>
        /// Sets an item to be in drag state and the position relative to the listbox.
        /// </summary>
        /// <param name="item">Item to be in drag state, otherwise null.</param>
        /// <param name="y">top position relative to the listbox for the specified item.</param>
        public void DragItem(ItemBase item, int y)
        {
            throw new NotImplementedException();
        }

        protected override void PreLayout()
        {
            if (CheckFlag(InvalidationFlag.Size))
            {
                if (CheckFlag(InvalidationFlag.Width))
                {
                    int w = ClientWidth;
                    foreach (var item in items)
                    {
                        item.Width = w;
                    }
                }
                if (itemHeightChangedStartIndex >= 0)
                {
                    items.ItemHeightChanged(itemHeightChangedStartIndex);
                    itemHeightChangedStartIndex = -1;
                }
            }
            base.PreLayout();
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout))
            {
                Margin m = ContainerMargin;
                Size size = new Size(Width, VirtualHeight + m.Height);
                container.Bounds = m.Layout(new Rectangle(0, 0, ClientWidth, VirtualHeight), size);
                LayoutItems((InvalidationMask & InvalidationFlag.UpdateChildren) == 0);
                container.Invalidate(InvalidationFlag.LayoutAppearance);
            }
            if (CheckFlag(InvalidationFlag.UpdateChildren))
            {
                UpdateItemProperties();
            }
            base.OnLayout();
        }

        private void UpdateItemProperties()
        {
            Control c = container.VisibleControls.First;
            while (c != null)
            {
                ItemBase item = c as ItemBase;
                if (item != null) OnUpdateItemProperties(item);
                c = c.NextControl;
            }
        }

        protected virtual void OnUpdateItemProperties(ItemBase item)
        {
            item.OnUpdateProperties();
        }

        private int CountChildControls(int topIndex, int bottomIndex)
        {
            int n = bottomIndex - topIndex + 1;
            return n;
        }

        /// <summary>
        /// Adds all items which are currently visible in the container.
        /// </summary>
        protected virtual void LayoutItems(bool notifyItems)
        {
            int yOffset = this.yOffset;

            int topIndex = Math.Max(items.GetItemIndexAt(yOffset), 0);
            int bottomIndex = items.Count > 0 ? Math.Max(items.GetItemIndexAt(yOffset + Height - 1), 0) : -1;

            yOffset = SetYOffset(yOffset);

            int n = CountChildControls(topIndex, bottomIndex);
            ControlCollection controls = container.VisibleControls;
            controls.Clear();
            int w = container.Width;
            int h = ClientHeight;
            for (int i = topIndex; i <= bottomIndex; i++)
            {
                ItemBase item = items[i];
                item.Width = w;
                controls.AddLast(item);

                ItemGroupBase group = item as ItemGroupBase;
                if (group != null) LayoutItemGroup(group, i, yOffset, topIndex, bottomIndex, h);

                if (notifyItems && (i < oldTopIndex || i > oldBottomIndex)) OnUpdateItemProperties(item);
            }


            oldTopIndex = topIndex;
            oldBottomIndex = bottomIndex;
        }

        private void LayoutItemGroup(ItemGroupBase group, int index, int yOffset, int topIndex, int bottomIndex, int clientHeight)
        {
            int to = Math.Max(0, yOffset - group.Top);
            if (AlwaysShowFirstItemHeader && (index == topIndex))
            {
                if (index == bottomIndex)
                {
                    group.SetClip(to, clientHeight - (group.Top - yOffset));
                }
                else
                {
                    group.SetClip(to, null);
                }
            }
            else if (index == bottomIndex)
            {
                group.SetClip(0, clientHeight - (group.Top - yOffset));
            }
            else
            {
                group.SetClip(0, null);
            }
        }

        public void Add(ItemBase item)
        {
            items.Add(item);
        }

        /// <summary>
        /// Sets the yOffset that is actually visualized and returns the visualized yOffset.
        /// </summary>
        protected virtual int SetYOffset(int value)
        {
            container.Y = -value;

            if (Squeeze && value < 0)
            {
                value = value * 3 / 4;
                // squeeze the container in case of YOffset is less than 0:
                int h = Math.Max(Height + (3 * value / 4), 0);
                float squeeze = (float)h / (float)Height;
                containerStretch.Y = squeeze;
                container.Transformation = containerStretch;
                container.Y = -value;
                value = 0;
            }
            else container.Transformation = null;


            return value;
        }

        /// <summary>
        /// Gets a  <see cref="ItemBase"/>.
        /// </summary>
        /// <param name="group"><see cref="ListBoxSecition"/> that holds the item, otherwise null.</param>
        /// <param name="index">Index of the item. If group is not null, this is the index in group.</param>
        /// <returns>ItemBase.</returns>
        public virtual ItemBase GetVirtualItem(ItemGroupBase group, int index)
        {
            if (GetItem != null)
            {
                ItemGroupEventArgs e = new ItemGroupEventArgs(index, group);
                GetItem(this, e);
                ItemBase item = e.Item;
                return item;
            }
            else throw new NotImplementedException("GetItem");
        }

        /// <summary>
        /// If overriden determines if the specified item of the specified group is detachable and performs additional operations to detach.
        /// Note that you must also override <see cref="CanDetach"/> if no DetachItem event is added and return true. Otherwise the method never occurs.
        /// </summary>
        /// <returns>True if the item is detached, otherwise false.</returns>
        public virtual bool OnCanDetachItem(ItemGroupBase group, ItemBase item)
        {
            if (DetachItem != null)
            {
                DetachItemEventArgs e = new DetachItemEventArgs(item, group);
                DetachItem(this, e);
                return e.Item == null;
            }
            return false;
        }

        public void ItemHeightChanged(ItemBase item)
        {
            Invalidate(InvalidationFlag.Size | InvalidationFlag.Layout | InvalidationFlag.Width);
            int index = items.IndexOf(item);
            if (itemHeightChangedStartIndex < 0 || itemHeightChangedStartIndex > index) itemHeightChangedStartIndex = index;
        }

        protected internal override void OnTap(TapEventArgs e)
        {
            //ItemBase item = e.GetDescendantControl(this, typeof(ItemBase)) as ItemBase;
            //if (item != null) OnItemTap(item, e);
            //base.OnTap(e);
        }

        protected virtual void OnItemTap(ItemBase item, TapEventArgs e)
        {
            //if (TapItem != null)
            //{
            //    ItemGroupBase group = item as ItemGroupBase;
            //    if (group != null)
            //    {
            //        item = e.GetDescendantControl(group, typeof(ItemBase)) as ItemBase;
            //    }
            //    TapItem(this, new ListBoxItemEventArgs { Item = item, Group = group });
            //}
        }

        internal void InvalidateItems()
        {
            Invalidate(InvalidationFlag.Layout);
        }

        /// <summary>
        /// Ensures that the given Item is visible inside ListBox.
        /// </summary>
        /// <param name="item">The item to be visible.</param>
        public virtual void EnsureItemVisible(ItemBase item)
        {
            int top = item.Top;
            int min = yOffset;
            int max = yOffset + Height;
            int bottom = item.Bottom;
            if (top < min)
            {
                GoToYOffset(top);
            }
            else if (bottom > max)
            {
                GoToYOffset(bottom - Height);
            }
        }

        internal protected virtual void OnItemRemoved(ItemBase item, ItemGroupBase group)
        {
            OnCanDetachItem(group, item);
            if (ItemRemoved != null)
            {
                ListBoxItemEventArgs e = new ListBoxItemEventArgs { Item = item, Group = group };
                ItemRemoved(this, e);
            }
        }

        public void RemoveAnimated(ItemBase item)
        {
            Animator.RemoveAnimated(item);
        }

        public void RemoveAnimated(int index)
        {
            Animator.RemoveAnimated(index);
        }

        public void InsertAnimated(int index, ItemBase item)
        {
            Animator.InsertAnimated(index, item);
        }

        protected virtual void OnSelectedItemChanged(ItemBase oldValue)
        {
            if (oldValue != null) oldValue.IsSelected = IsItemSelected(oldValue);
            if (selectedItem != null) selectedItem.IsSelected = IsItemSelected(selectedItem);
        }

        /// <summary>
        /// Determines if the specified item is selected.
        /// </summary>
        /// <param name="item">Item to check.</param>
        /// <returns>True if the item is selcted, otherwise false.</returns>
        public virtual bool IsItemSelected(ItemBase item)
        {
            return selectedItem == item;
        }


        protected internal override void OnCommand(CommandEventArgs args)
        {
            if (args.Handled) return;
            if (args.Command == ListBoxBase.SelectItemCommand)
            {
                ItemBase item = args.Owner as ItemBase;
                if (item != null) SelectedItem = item;
                args.Handled = true;
            }
            base.OnCommand(args);
        }

        /// <summary>
        /// Brings the specified item into view.
        /// </summary>
        /// <param name="item">Item to bring into view. Must be member of Items.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        public virtual void BringItemIntoView(ItemBase item, bool animated)
        {
            if (container != null) container.BringIntoView(item.Bounds, animated);
        }

        #endregion
        #region events

        public event EventHandler<ListBoxItemEventArgs> TapItem;

        public event EventHandler<ListBoxItemEventArgs> ItemRemoved;

        public event EventHandler<ItemGroupEventArgs> GetItem;

        public event EventHandler<DetachItemEventArgs> DetachItem;

        #endregion


        /// <summary>
        /// Brings a rectangle into view.
        /// </summary>
        /// <param name="rect">Rectangle to bring into view.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        internal  void BringRectIntoView(Rectangle rect, bool animated)
        {
            if (container != null) container.BringIntoView(rect, animated);
        }

        public override void BringIntoView(Rectangle bounds, bool animated)
        {
            bounds.Offset(0, YOffset);
            base.BringIntoView(bounds, animated);
        }
    }
}
